/**
 * Created by JetBrains WebStorm.
 * User: hshober
 * Date: 11/10/11
 * Time: 1:43 PM
 * To change this template use File | Settings | File Templates.
 */
var xforms = xforms || {
    classname:"xforms",
    init: function(){
        xforms.models = {};
        xforms.templates = {};
        xforms.indexes = {};
    },
    clear: function(){
        xforms.removePages();
        xforms.removeScripts();
        xforms.init();
    },
    removePages: function (){
        $("div").not("#loading").each(function(){
            $(this).remove();
        });
    },
    removeScripts: function (){
        $("script[data-role='model']").each(function(){
            $(this).remove();
        });
    },
    defaultContext: {
        model: null
    }
};
xforms.init();

// jquery plugins
// data selector
(function($){
    var _dataFn = $.fn.data;
    $.fn.data = function(key, val){
        if (typeof val !== 'undefined'){
            $.expr.attrHandle[key] = function(elem){
                return $(elem).attr(key) || $(elem).data(key);
            };
        }
        return _dataFn.apply(this, arguments);
    };
})(jQuery);
// XML serializer
$.fn.serializeXML = function () {
    var out = '';
    if (typeof XMLSerializer != 'undefined') {
        var xs = new XMLSerializer();
        this.each(function() {
            out += xs.serializeToString(this);
        });
    } else if (this[0] && this[0].xml != 'undefined') {
        this.each(function() {
            out += this.xml;
        });
    }
    return out;
};
$.fn.xpath = function (path, value) {
    var xpath = path;
    var model;
    var record=null;
    var context = this.context || this[0];
    var attribute;

    //Split out instance id from the path
    var instanceId;
    var re = new RegExp(/instance\(['"](.*)['"]\)(.*)/gi);
    var parts = re.exec(path);
    if (parts != null) {
        var instanceId = parts[1];
        path = parts[2];
    }
    try {
   // Set context if [this] is a model or instance
    if (context.classname=="model") {
        model = context;
        if (instanceId==null) instanceId = context.defaultInstance.id;
        context = context.instances[instanceId].content;
    } else if (context.classname=="instance"){
        model = context.model;
        if (instanceId==null || instanceId==context.id){
            context = context.content;
        } else {
            context = context.model.instances[instanceId].content;
        }
    } else {
        context = this;
    }

    // Split out the attribute if identified
    var fieldsPattern = new RegExp(/^(.*)(\.|@[a-zA-Z0-9\-_:]*)$/gi);
    parts = fieldsPattern.exec(path);
    if (parts != null) {
        path = parts[1];
        attribute = parts[2];
    }

    // Convert the xpath to css selector
    if(path!=null && path!=""){
        path = path.replace(/\/$/g, "");
        //path = path.replace(/^(\/\/)/g, "");
        //path = path.replace(/^\//g, "");
        path = path.replace(/\/\//g, " ");
        path = path.replace(/\//g, " > ");
        path = path.replace(/:/g, "|");
        path = path.replace(/@/g, "");
        if(path.indexOf(" ")!=0) path = " > " + path;
        

    	if(context.context.documentElement!=undefined){
    		context = $(context.context.documentElement);
    	}
		record = context.find(path);
        if(record.length == 0 ){
    		var loc = path.indexOf(" > ");
            if (loc!=-1) {
    	    	if(loc==0)path = path.substr(loc + 3);
    	        loc = path.indexOf(" > ");
    	        if (loc!=-1) {
    	            var root = path.substr(0,loc);
    	            path = path.substr(loc);
    	            record = context.filter(root).find(path);	                
    	        }else{
    	        	record = context.filter(path);
    	        }
            }else{
            	record = context.filter(path);
            }
    	}
    } else {
        record = context;
    }

    //Fetch or set the attribute value if attribute specified
    if(record!=null){
        if(attribute!=null){
            var attr = {
                classname: "attribute",
                name: attribute,
                path: path,
                record: record,
                xmlText: function(value){
                    if (value==null) {
                        if (this.name=="."){
                            return this.record.text();
                        } else if (attribute.substr(0,1)=="@") {
                            return this.record.attr(attribute.substr(1));
                        }
                    } else {
                        if (this.name!=null && this.name.substr(0,1)=="@") {
                            this.record.attr(this.name.substr(1),value);
                        } else {
                            this.record.text(value);
                        }
                        var event = jQuery.Event("xforms-value-changed");
                        event.record = this.record;
                        event.value = value;
                        $(this.record).trigger(event);
                    }
                },
                bind: function(event, func){
                    this.record.bind(event,func);
                },
                trigger: function(event){
                    this.record.trigger(event);
                }
            }
            record = attr;
        } else {
            if (record) {
                applyXMLTextToNode(record);
            }

        }
    }
    } catch (e) {
        if (model==null) model = xforms.defaultContext.model;
        var event = jQuery.Event("xforms-binding-exception");
        event.xpath = xpath;
        $(model).trigger(event);
        return null;
    }
    return record;
};
function applyXMLTextToNode(node){
    var $node = node;
    if ($node.context==null) {
        $node = $(node);
    }
    $node.xmlText = function(value){
        if (value==null) {
            return this.text();
        } else {
            this.text(value);
            var event = jQuery.Event("xforms-value-changed");
            event.record = this;
            event.value = value;
            $(this).trigger(event);
        }
    }
    return $node;
}
jQuery.extend(jQuery.expr[':'], {
    nodeset: function (e, index, match, array) {
    if ($(array[index]).data("nodeset")==null) return false;
    for (var i = index -1; i >= 0; i--) {
      if ($(array[i]).data("nodeset")!=null || $(array[i]).data("ref")!=null) {
        var parents = $(e).parents();
        for (var j = parents.length-1; j >= 0; j--){
            if (array[i] == parents[j]) return false;
        }
      }
    }
    return true;
    },
    dataContainer: function (e, index, match, array) {
    var ref = $(e).data("nodeset") || $(e).data("ref");
    if (ref==null) return false;
    var tags = {"div":true,"fieldset":true,"span":true};
    if ($(e).data("nodeset")==null && tags[e.localName]!=true) return false;
    for (var i = index -1; i >= 0; i--) {
      if ($(array[i]).data("nodeset")!=null || $(array[i]).data("ref")!=null) {
        var parents = $(e).parents();
        for (var j = parents.length-1; j >= 0; j--){
            if (array[i] == parents[j]) return false;
        }
      }
    }
    return true;
    },
    ref: function (e, index, match, array) {
      if ($(array[index]).data("ref")==null) return false;
      for (var i = index -1; i >= 0; i--) {
        if ($(array[i]).data("nodeset")!=null || $(array[i]).data("ref")!=null) {
          var parents = $(e).parents();
          for (var j = parents.length-1; j >= 0; j--){
              if (array[i] == parents[j]) return false;
          }
        }
      }
      return true;
    }
});

$(document).ready(function(){
    loadXForms(document)
});
function loadXForms(root){
  // Load Models
    $(xforms).bind("xforms-model-construct-done", function(){
        if (xforms.unprocessedModels == 0) {
            $(document).find("[data-role='page']").each(function(){
                bindNodesetsAndSingleNodes(xforms.defaultContext, this);
                bindSuggest(this);
            });
            $.each(xforms.models,function(model){
                $(model).trigger("xforms-ready");
            })
            var event = jQuery.Event("xforms-ready");
            $(root).trigger(event);
        }
    });
	loadModels(root);
}
function loadModels(xml){
    $.xmlns["xf"] = "http://www.w3.org/2002/xforms";
    var root = xml || document;
    var nstext = "";
    $(document).find("html").first().each(function(index){
        $.each(this.attributes, function(i, attrib){
            var name = attrib.name;
            var value = attrib.value;
            if (name.indexOf("xmlns")==0) nstext = nstext + name + "=\"" + value + "\" ";
        });
    });
    xforms.unprocessedModels = 0;
    var modelGUID = 0;
    $(root).find("script[data-role='model']").each(function(index){
        var xml = $(this).html();
        if ( nstext != null && xml.indexOf("xmlns")==-1 ) {
            xml = xml.replace(/<([^>]+)>/, '<\$1 '+nstext+'>');
        }
        xml = $.parseXML(xml);
        var modelCount = $(xml).find("xf|model").length;
        xforms.unprocessedModels = xforms.unprocessedModels + modelCount;
        $(xml).find("xf|model").each(function(){
            var model = {
                classname: "model",
                instances:{},
                submissions:{},
                xml: this,
                find: function(path){
                    var nodeset;
                    nodeset = $(this).xpath(path);
                    return nodeset;
                },
                setValue: function(path, value){
                    var nodeset;
                    nodeset = $(this).xpath(path).xmlText(value);
                    return nodeset;
                },
                getInstance: function (path) {
                    var model = this;
                    var instance = null;
                    var re = new RegExp(/instance\(['"](.*)['"]\)(.*)/gi);
                    var parts = re.exec(path);
                    if (parts != null) {
                        instance = model.instances[parts[1]];
                    } 
                    if (instance==null) {
                    	instance = model.instances[path];
                    }
                   return instance;
                }
            };
            model.id = $(this).attr("id") || 'model_' + modelGUID++;
            xforms.models[model.id] = model;
            
            if (xforms.defaultContext.model == null) {
                xforms.defaultContext.model = model;
            }
            var attributes = this.attributes; // returns an array of attributes?
            $.each(attributes, function (i, attribute) {
                if (attribute.prefix=="xmlns"){
                    var uri = attribute.value;
                    var prefix = attribute.localName;
                    $.xmlns[prefix] = uri;
                }
            })
            var instanceCount = 0;
            $(this).find("xf|instance").each(function(){
                var url = $(this).attr("src") || $(this).attr("resource");
                var id = $(this).attr("id") || 'instance_' + instanceCount++;
                var instance = {
                    classname: "instance",
                    id: id,
                    url: url,
                    method: "GET",
                    type: "xml",
                    model: model,
                    find: function(path){
                        var nodeset;
                        nodeset = $(this).xpath(path);
                        return nodeset;
                    },
                    setValue: function(path, value){
                        var nodeset;
                        nodeset = $(this).xpath(path).xmlText(value);
                        return nodeset;
                    }
                };
                if (this.childElementCount > 0){
                    instance.content = this.firstElementChild;
                } else content = null;
                model.instances[instance.id] = instance;
                if (model.defaultInstance==null) {
                    model.defaultInstance=instance;
                }
            })
            $(this).find("xf|submission").each(function(){
                var submission = {};
                submission.id = $(this).attr("id") || 'default';
                submission.ref = $(this).attr("ref");
                submission.instance = $(this).attr("instance");
                submission.method = $(this).attr("method");
                submission.action = $(this).attr("action") || $(this).attr("resource");
            	submission.actionRef = $(this).find("resource").attr("value");
                submission.async = ($(this).attr("mode")=="synchronous") ? false : true;
                submission.replace = $(this).attr("replace");
                submission.xml = this;
                submission.model = model;
                model.submissions[submission.id] = submission;
                bindActions(model, submission);
                $(submission).bind("xforms-submit", function(event){
                   onSubmit(this, event);
                });
            })
            $(model).bind("xforms-model-construct-done", function(){
                --xforms.unprocessedModels;
                var event = jQuery.Event("xforms-model-construct-done");
                $(xforms).trigger(event);
            });
            $(model).bind("xforms-model-construct", function(){
                bindActions(model, model);
                loadModelData(model);
                var event = jQuery.Event("xforms-model-construct-done");
                $(model).trigger(event);
            });
        })
    })
    $.each(xforms.models, function(key, model){
        var event = jQuery.Event("xforms-model-construct");
        $(model).trigger(event);

    });
}
function loadModelData(model){
    // Load Namespaces
    $(model.namespaces).each( function(index){
        $.xmlns[this.prefix] = this.URI;
    });
    // Load Instances
    $.each(model.instances, function(key, instance){
        if (instance.url!=null){
            $.ajax( {
                context: instance,
                async: false,
                type: instance.method,
                url: instance.url,
                dataType: instance.type,
                success: processInstanceXml
            });
        } else {
            loadStaticInstance(instance);
        }
    });
}
//Now that that's out of the way, we can start parsing the XML. As you can see, when the request succeeds, the function parseXML is called. That's where I'm going to put my code. Let's start by finding the author of each tutorial, which are stored as attributes on the Tutorial tag.
function processInstanceXml(xml){
    var $data = $(xml)
    this.content = $data;
    $(this).trigger("xforms-refresh");
}
function loadStaticInstance(instance){
    //var xml = $.parseXML(model.content);
    var xml = instance.content;
    var $data = $(xml)
    instance.content = $data;
    $(this).trigger("xforms-refresh");
}
function resetValue(el){
    var context = $(el).data("context");
    // Set the value
    if (context != null && context.ref != null){
        var value = context.ref.xmlText();
        var expression = $(el).data("value");
        if (expression!=null) {
            expression = "var ref='"+value+"';" + expression;
            value = eval(expression);
        }
        switch (el.localName) {
            case "a":
            case "li":
            case "button":
                $(el).data("value", value);
                break;
            case "legend":
            case "label":
            case "span":
                $(el).text(value);
                break;
            case "img":
                $(el).attr("src", value);
                break;
            default:
                $(el).val(value);
        }
    }
    if (context != null && context.label != null){
        var value = context.label.xmlText();
        $(el).text(value);
    }
    if (el.type == 'radio' || el.type == 'checkbox'){
        var checked = false;
        if (context!=null && context.copy != null){
            var copyTo = getControlGroupContext(context).ref;
            var origin = context.copy;
            var node = findNodeInSet($(copyTo).children(), origin);
            checked = (node!=null);
        } else if (context!=null && context.ref != null){
            var target = getControlGroupContext(context).ref.xmlText();
            var origin = context.ref.xmlText();
            checked = (target == origin);
        } else if (context!=null) {
            var target = getControlGroupContext(context).ref.xmlText();
            var origin = $(el).val();
            checked = (target == origin);
        } else {
            checked = el.checked;
        }
        if (checked) {
            $(el).attr('checked',true);
            //el.checked = true;
        } else {
            $(el).attr('checked',false);
            //el.checked = false;
        }
        try {
            $(el).checkboxradio("refresh");
        } catch (e){
          // Do nothing
        }
    }
    if (el.localName=="select"){
        try {
            $(el).selectmenu("refresh", true);
        } catch (e){
          // Do nothing
        }
    }
}
function findNodeInSet(nodeset, node){
    var foundNode;
    var xml = $(node).serializeXML();
    $(nodeset).each(function(){
        var item = this;
        if ($(item).serializeXML() == xml ) {
            foundNode = item;
            return;
        }
    });
    return foundNode;
}
function refreshNodeset(element, parentContext, template){
    var context = resolveContext(element, parentContext, "nodeset");
    if (context == null) return;
    var nodeset = context["nodeset"];
    if(nodeset == null) return context;
    $(element).empty();
    var index = 1;
    nodeset.each(function(){
        var el = $(template).clone(true);
        var repeatingcontext = {
            "model": context.model,
            "instance": context.instance,
            "parent": context,
            "index": index,
            "repeatKey": element.id,
            "ref": applyXMLTextToNode(this)
        };
        bindNodesetsAndSingleNodes(repeatingcontext, el);
        if (index > 1){
            el.find("[id],[for]").each(function(){
                var child = this;
                var id = $(child).attr("id");
                if (id!=null) {
                    $(child).attr("id", id + "-" + index);
                }
                var fid = $(child).attr("for");
                if (fid!=null) {
                    if (fid!=null) $(child).attr("for", fid + "-" + index);
                }
            });
        }
        el.children().each(function(){
            var child = this;
            $(element).append(child);
        });
        if (!context.repeat) return false;
        index++;
    })
    applyStyleToGroup(element);
    return context;
}
function applyStyleToGroup(element){
    $(element).find("[data-target-role]").each(function(){
        var targetRole = $(this).attr("data-target-role");
        if (targetRole) $(this).attr("data-role", targetRole);
    });
    $(element).find("[data-role='none']").each(function(){
        $(this).removeAttr("data-role");
    });
    $(element).find("[data-role]").each(function(){
        var role = $(this).attr("data-role");
        switch (role){
            case "fieldcontain":
                $(this).trigger("create");
                break;
            case "controlgroup":
            case "page":
            case "model":
            case "main":
            case "content":
            case "group":
            case "header":
            case "footer":
            case "collapsible":
            case "button":
            case "listview":
                break;
            default:
                $(this).trigger("create");
        }
    });
    switch (element.localName){
        case "select":
            if ($(element).attr("data-role")=="none") $(element).removeAttr("data-role")
            $(element).selectmenu();
            $(element).selectmenu("refresh");
            break;
        default:
    }
}
function getTemplate(el){
    var id = el.id;
    if (!(id)) {
        var newDate = new Date;
        id = "id_" + newDate.getTime();
        $(el).attr("id", id);
    }
    var template = xforms.templates[id] || $(el).clone(true);
    xforms.templates[id] = template;
    return template;
}

function resolveContext(element, parentContext, contextType){
    var context = $(element).data("context");
    if (context==null) {
        context = {"parent":parentContext,"element":element};
        $(element).data("context", context);
        var modelid = $(element).data("model");
        context.model = xforms.models[modelid] || parentContext.model;
    }
//    if (context[contextType]!=null) return context;
    var model = context.model;
    var xpath = null;
    if (contextType=="nodeset") {
        xpath = $(element).data("nodeset") || $(element).data("ref") ;
        context.repeat = ($(element).data("nodeset")!=null);
    } else {
        xpath = $(element).data(contextType);
    }
    if (xpath==null) return context;
    var explicit = (xpath.indexOf("instance(")==0);
    //Explicit instance references override default location in XML structure
    if(explicit) {
        context.instance = model.getInstance(xpath) || model.defaultInstance;
        context[contextType] = $(model).xpath(xpath) || null;
        context.repeatKey = "none";
        context.index = 1;
    } else {
        context.repeatKey = parentContext.repeatKey;
        context.index = parentContext.index;
        var parentNodeContext;
        parentNodeContext = parentContext.nodeset || parentContext.ref;
        context.instance = parentContext.instance || model.defaultInstance;
        if (parentNodeContext==null){
            parentNodeContext = context.instance.content;
        }
        context[contextType] = $(parentNodeContext).xpath(xpath);
    }
    return context;
}

var outputelements = {output:"output",span:"span",fieldset:"fieldset",option:"option",a:"a",li:"li",button:"button",label:"label",legend:"legend"};

function bindNodesetsAndSingleNodes(parentContext, parent){
    var display = parent || document;
    $(display).find(":dataContainer").each(function(){
        var element = this;
        var bound = $(element).data("bound-nodeset");
        if (bound) return;
        var name = element.localName;
        if ($(element).data("nodeset")==null) {
            var context = refreshSingleNode(element, parentContext);
            //$(element).trigger("create");
            $(context.instance).bind("xforms-refresh xforms-insert xforms-delete", function(event){
                context.ref = null;
                refreshSingleNode(element, parentContext);
                //$(element).trigger("create");
            });
        } else {
            var template = getTemplate(element);
            var context = refreshNodeset(element, parentContext, template);
            $(context.instance).bind("xforms-refresh xforms-insert xforms-delete", function(event){
                 context.nodeset = null;
                 refreshNodeset(element, parentContext, template);
            });
        }
        $(element).data("bound-nodeset",true);
    });
    $(display).find(":input,output[data-ref],option[data-ref],li[data-ref],a[data-ref],button[data-ref],label[data-ref],legend[data-ref],img[data-ref]").each(function(){
        var element = this;
        var context = bindSingleNodesByContext(element, parentContext);
    });

    bindSubmission(display, parentContext);
    applyStyleToGroup(display);
}
function refreshSingleNode(element, parentContext){
    var context = bindSingleNodeContainerContext(element, parentContext);
    if (context !=null ) bindNodesetsAndSingleNodes(context, element);
    return context;
}
function bindSingleNodeContainerContext(element, parentContext){
    //exit if bound;
    var context = resolveContext(element, parentContext, "ref");
    resolveContext(element, parentContext, "label");
    resolveContext(element, parentContext, "copy");
    return context;
}
function bindSingleNodesByContext(element, parentContext){
    //exit if bound;
    var bound = $(element).data("bound");
    if (bound) return;
    var context = resolveContext(element, parentContext, "ref");
    var node = context.ref;
    $(node).bind("xforms-value-changed", function(event){
        resetValue(element);
    });
    resolveContext(element, parentContext, "label");
    var label = context["label"];
    $(label).bind("xforms-value-changed", function(event){
        resetValue(element);
    });
    resolveContext(element, parentContext, "copy");
    var copy = context["copy"];
    $(copy).bind("xforms-value-changed", function(event){
        resetValue(element);
    });
    resetValue(element);
    if (!(outputelements[element.localName])) bindInputElement(element)
    //$(element).trigger("create");
    if ($(element).data("bound")==null){
        $(context.instance).bind("xforms-refresh xforms-insert xforms-delete", function(event){
            $(element).data("bound",false);
        });
    }
    $(element).data("bound",true);
    return context;
}
function bindInputElement(element){
    // Apply value change back to instance
    var type = $(element).attr("type");
    $(element).bind("paste change", function(event){
        setTimeout(function(){
            onValueChange(element);
        }, 0);
    });
    if (type == "radio" || type == "checkbox") {
        var context = $(element).data("context");
        var containerContext;
        var groupContext = getControlGroupContext(context);
        if (groupContext!=null) containerContext = groupContext.ref;
        if (containerContext!=null){
            $(containerContext).bind("xforms-value-changed", function(event){
                resetValue(element);
            });
        }
    }
    var incremental = $(element).data("incremental");
    if (incremental){
        var needsDelay = true;
        var charactersPressed = 0;
        var skip = false;
        $(element).keypress(function(event){
            charactersPressed++;
            if (!skip) {
                skip = true;
                needsDelay = (charactersPressed<=3);
                if(needsDelay) {
                    setTimeout(function(){
                        onValueChange(element);
                        charactersPressed = 0;
                        $(element).trigger("incremental-change");
                        skip = false;
                    }, 1500);
                } else {
                    setTimeout(function(){
                        onValueChange(element);
                        charactersPressed = 0;
                        $(element).trigger("incremental-change");
                        skip = false;
                    }, 0);
                }
            }
        });
    }
}
function bindSubmission(display, parentContext){
	$(display).find("button[data-submission],a[data-submission]").each(function(){
        var button = this;
        var subId = $(button).data("submission");
        var context = resolveContext(button, parentContext, "ref");
        if (context) {
            var model = context.model;
            var submission = model.submissions[subId];
            $(button).click( function(event){
                $(submission).trigger("xforms-submit");
            });
        }
    });
    $(display).find(":input[data-submission]").each(function(){
        var input = this;
        var subId = $(input).data("submission");
        var context = resolveContext(input, parentContext, "ref");
        if (context) {
            var model = context.model;
            var submission = model.submissions[subId];
            $(input).change( function(event){
                setTimeout(function(){
                    $(submission).trigger("xforms-submit");
                }, 0);
            });
        }
    });
}
function getControlGroupContext(context){
    var controlGroupContext=null;
    if (context==null) return;
    var element = context.element;
    if (element!=null) {
        var role = $(element).data("target-role") || $(element).data("role");
        if (role=="controlgroup") {
            controlGroupContext = context;
        } else {
            controlGroupContext = getControlGroupContext(context.parent);
        }
    } else {
        controlGroupContext = getControlGroupContext(context.parent);
    }
    return controlGroupContext;
}
function onValueChange(el){
    var context = $(el).data("context");
    if (el.type == 'radio' || el.type == 'checkbox'){
        if (context != null && context.copy != null){
            var copyTo = getControlGroupContext(context);
            if (copyTo != null){
                var origin = context.copy;
                if (el.checked) {
                    var originClone = $(origin).clone();
                    $(copyTo.ref).append(originClone);
                } else {
                    var node = findNodeInSet($(copyTo.ref).children(), origin);
                    if (node!=null) {
                        $(node).remove();
                    }
                }
                var ev = jQuery.Event("xforms-refresh");
                ev.context = copyTo;
                $(copyTo.instance).trigger(ev);
            }
        } else if (context != null){
            var value = "";
            var target;
            var groupContext = getControlGroupContext(context);
            if (groupContext!=null) {
                target = groupContext.ref;
                if (el.checked) {
                    if(context.ref != null){
                        value = context.ref.xmlText();
                    }else{
                        value = el.value;
                    }
                }
                target.xmlText(value);
            }
        }
    } else {
        var value = el.value;
        context.ref.xmlText(value);
        var ev = jQuery.Event("xforms-value-changed");
        ev.context = context;
        ev.value = value;
        $(context.instance).trigger(ev);
    }
    if (el.localName=="select"){
        try {
            $(el).selectmenu("refresh",true);
        } catch (e){
          // Do nothing
        }
    }

}
function bindSuggest(display){
    $(display).find("ul[data-role='suggest']").each(function(elemCount){
        var $listTemplate = $(this);
        var tagId = "#" + $listTemplate.attr("for");
        var input = $.find(tagId);
        if (input != null){
            var targetModel = $listTemplate.data("model") || xforms.defaultContext.model.id;
            var submissionId = $listTemplate.data("submission");
            var model = xforms.models[targetModel];
            $( input ).autocomplete({
                source: function(req, res){
                    var suggestions = getSuggestionsFromInstance($listTemplate);
                    res(suggestions);
                },
                search: function(event, ui){
                    onValueChange(this);
                    if (submissionId!=null){
                        var submission = model.submissions[submissionId];
                        $(submission).trigger("xforms-submit");
                    }
                    return true;
                },
                select: function(event, ui){
                    setTimeout(function(){
                        input.value = ui.item.value;
                        onValueChange(input);
                    },0);
                }
            });
        }
    });
}
function getSuggestionsFromInstance(el){
    var suggestions = [];
    el.find("li[data-ref]").each(function(){
        var item = this;
        var value = $(item).data("value");
        var label = $(item).text();
        suggestions.push({"label":label,"value":value});
    });
    return suggestions;
}
function bindActions(model, target){
    var xml = target.xml;
    $(xml).find("> xf|action,> xf|send,> xf|insert,> xf|delete,> xf|setvalue,> xf|message").each(function(){
        var action = {
            classname: "action",
            event: $(this).attr("ev:event"),
            expression: $(this).attr("if"),
            tasks: []
        };
        action.element = $(this).attr("ev:observer");
        action.model = model;
        if(action.element!=null){
            $(action.element).live(action.event, function(event){
                var element = this;
                setTimeout(function(){
                    executeAction(element, action);
                }, 0);
            });
        }else{
            $(target).bind(action.event, function(event){
                var element = this;
                setTimeout(function(){
                    executeAction(element, action);
                }, 0);
            });
        }
        if (this.localName=="action"){
            $(this).children().each(function(){
                var task = createTask(this);
                task.action = action;
                action.tasks.push(task);
            });
        } else {
            var task = createTask(this);
            task.action = action;
            action.tasks.push(task);
        }
    });
}
function createTask(el){
    var type = el.localName;
    var task = {
        classname: "task",
        type: type,
        model: $(el).attr("model"),
        submission: $(el).attr("submission"),
        context: $(el).attr("context"),
        ref: $(el).attr("ref"),
        bind: $(el).attr("bind"),
        at: $(el).attr("at"),
        origin: $(el).attr("origin"),
        nodeset: $(el).attr("nodeset"),
        position: $(el).attr("position"),
        value: $(el).attr("value"),
        text: $(el).text(),
        resource: $(el).attr("resource"),
        show: $(el).attr("show"),
        expression: $(el).attr("if")
    };
    return task;
}

function executeAction(element, action){
    var context = $(element).data("context");
    if (context!=null){
        xforms.indexes[context.repeatKey] = context.index;
    }
    if (!evaluateIfExpression(action.expression, context, action.model)) return;
    $(action.tasks).each(function(){
        var task = this;
        var model = xforms.models[task.model] || action.model;
        if (!evaluateIfExpression(task.expression, context, model)) return;
        if(task.type=="send"){
            sendAction(element, task, model);
        } else if(task.type=="insert") {
            insertAction(element, task, model);
        } else if(task.type=="delete") {
            deleteAction(element, task, model);
        } else if(task.type=="setvalue") {
            setValueAction(element, task, model);
        } else if(task.type=="message") {
            alertAction(element, task, model);
        } else if(task.type=="load") {
        	loadAction(element, task, model);
        }
    })
}
function evaluateIfExpression(expression, context, targetModel){
    if(expression == null) return true;
    var re = new RegExp(/^(.+?)(==|<=|>=|!=|!<<|!>>|>>|<<)(.+)$/gi);
    var tokens = re.exec(expression);
    if (tokens != null) {
        var left = $.trim(tokens[1]);
        var operation = $.trim(tokens[2]);
        var right = $.trim(tokens[3]);
        var literalExp = new RegExp(/^['"](.*)['"]$/gi);
        var parts = literalExp.exec(left);
        if (parts == null) {
            if (left.indexOf("instance(")==0 || context == null){
            	if(targetModel.find(left).length > 0){
            		left = targetModel.find(left).xmlText();	
            	}else{
            		left = "";
            	}            	
            } else {
                left = $(context.ref).xpath(left).xmlText();
            }
            left = "'" + left + "'";
        }
        literalExp = new RegExp(/^['"](.*)['"]$/gi);
        parts = literalExp.exec(right);        
        if (parts == null) {
        	var rightList = null;
            if (right.indexOf("instance(")==0 || context == null){
            	if(targetModel.find(right).length > 0){
            		right = targetModel.find(right).xmlText();	
            	}else{
            		right = "";
            	}                
            } else {
                right = $(context.ref).xpath(right).xmlText();
            }
            right = "'" + right + "'";
        }
        var statement = "";
        switch (operation){
        case ">>":
        	statement = "(" + right + ".indexOf(" + left + ")!=-1)";
        	break;
        case "<<":
        	statement = "(" + left + ".indexOf(" + right + ")!=-1)";
        	break;
        case "!>>":
        	statement = "(" + right + ".indexOf(" + left + ")==-1)";
        	break;
        case "!<<":
        	statement = "(" + left + ".indexOf(" + right + ")==-1)";
        	break;
        default:
        	statement = "(" + left + operation + right + ")";        	
        }
        return eval(statement);
    } else {
        var value;
        if (expression.indexOf("instance(")==0 || context == null){
            value = targetModel.find(expression).xmlText();
        } else {
            value = $(context.ref).xpath(expression).xmlText();
        }
        return (value.length > 0);
    }
}
function loadAction(element, task, model){
	var linkUrl=null;
	if(task.ref){
        var context = $(element).data("context");
        var boundNode;
        if (context!=null) boundNode = context.ref;
        if(boundNode!=null){
            linkUrl =  $(boundNode).xpath(task.ref).xmlText();
        } else {
            linkUrl =  $(model).xpath(task.ref).xmlText();
        }
	}
	if(task.show==="new"){
		if(linkUrl){
			window.open(linkUrl);
		}else if(task.resource){
			window.open(task.resource);
		}
	}else{
		if(linkUrl){
			window.location.href = linkUrl;
		}else if(task.resource){
			window.location.href = task.resource;
		}
	}
}
function sendAction(element, task, model){
    var submission = model.submissions[task.submission];
    $(submission).trigger("xforms-submit");
}
function insertAction(element, task, targetModel){
    var action = task.action;
    var instance;
    var origin;
    var context = $(element).data("context");
    var boundNode;
    if (context!=null) boundNode = context.ref;
    if(boundNode!=null){
        origin = $(boundNode).xpath(task.origin);
    } else {
        origin = action.model.find(task.origin);
    }
    if (task.context!=null){
        context = targetModel.find(task.context);
        instance = targetModel.getInstance(task.context);
    } else {
        context = targetModel;
        instance = targetModel.defaultInstance;
    }
    if (task.nodeset!=null) {
        context = $(context).xpath(task.nodeset);
    }

    var position = (task.position!=null) ? task.position : "after";
    if (origin != null) {
        var originClone = $(origin).clone();
        if(task.nodeset!=null && task.at!=null){
            var at = task.at;
            //look for index function to set selector to :eq(index)
            var re = new RegExp(/index\(['"](.*)['"]\)/gi);
            var parts = re.exec(task.at);
            if (parts != null) {
                var repeatKey = parts[1];
                var index = xforms.indexes[repeatKey] -1;
                at = ":eq(" + index + ")";
            }
            var node = null;
            if(context.length == 0){
            	node = context.prevObject;
            	$(node).append(originClone);
            }else{
            	node = $(context).filter(at);
                if (position=="before"){
                    $(node).before(originClone);
                } else {
                    $(node).after(originClone);
                }
            }
        } else {
            $(context).append(originClone);
        }
        var event = jQuery.Event("xforms-insert");
        event.origin = task.origin;
        event.nodeset = task.nodeset;
        event.context = task.context;
        $(instance).trigger(event);
    }
}
function deleteAction(element, task, model){
    var action = task.action;
    var context = $(element).data("context");
    var boundNode;
    if (context!=null) boundNode = context.ref;
    if(boundNode!=null){
        if (task.context!=null){
            context = model.find(task.context);
        } else {
            context = boundNode;
        }
    } else {
        context = model.find(task.context);
    }
    if (task.nodeset!=null) {
        context = $(context).xpath(task.nodeset);
    }
    if(task.nodeset!=null && task.at!=null){
        var at = task.at;
        //look for index function to set selector to :eq(index)
        var re = new RegExp(/index\(['"](.*)['"]\)/gi);
        var parts = re.exec(task.at);
        if (parts != null) {
            var repeatKey = parts[1];
            var index = xforms.indexes[repeatKey]-1;
            at = ":eq(" + index + ")";
        }
        $(context).filter(at).remove();
    } else {
        $(context).filter(":first").remove();
    }
    var instance = model.getInstance(task.context);
    var event = jQuery.Event("xforms-delete");
    $(instance).trigger(event);
}
function alertAction(element, task, model){
    alert(task.text);
}
function setValueAction(element, task, targetModel){
    var action = task.action;
    var value = task.text;
    if(task.value!=null){
        var context = $(element).data("context");
        var boundNode;
        if (context!=null) boundNode = context.ref;
        if(boundNode!=null){
            value = $(boundNode).xpath(task.value).xmlText();
        } else {
            value = $(action.model).xpath(task.value).xmlText();
        }
    }
    var ref = targetModel.setValue(task.ref, value);
}

function onSubmit(submission, event){
    if (event!=null){
        event.preventDefault();
        event.stopPropagation();
    }
    $("body").mask();
    var method = submission.method.toUpperCase();
    $.mobile.loadingMessage = method=="GET" ? 'Loading' : 'Saving';
    $.mobile.showPageLoadingMsg();
    var model = submission.model;
    var xml = null;
    var data = null;
    if (submission.ref!=null){
        data = model.find(submission.ref);
    }
    var url = null;
    if(submission.action == undefined && submission.actionRef != undefined){
    		url = $(model).xpath(submission.actionRef).xmlText();
    		var test = null;
    }else{
    	url = submission.action;
    }
    if(method=="POST" || method=="PUT"){
    	if(data!==null){
        	xml = $(data).serializeXML();
    	}
        $.ajax({
            contentType: 'application/xml',
            context: submission,
            data: xml,
            dataType: 'xml',
            async: submission.async,
            processData: false,
            success: onSubmitSuccess,
            error: function(jqXHR, textStatus, errorThrown) {
            	$.mobile.hidePageLoadingMsg();
                $("body").unmask();
                alert(textStatus);
            },
            timeout: 60*1000,
            type: method,
            url: url
        });
    } else if (method=="GET") {
    	var queryString = "";
    	if(data!==null){
    		queryString = domToQuery(data);
    	}
        $.ajax({
            context: submission,
            dataType: 'xml',
            async: submission.async,
            processData: false,
            success: onSubmitSuccess,
            error: function(jqXHR, textStatus, errorThrown) {
                $("body").unmask();
                var event = jQuery.Event("xforms-submit-error");
                event.submission = submission;
                event.textStatus = textStatus;
                event.errorThrown = errorThrown;
                $(submission.model).trigger(event);
            },
            timeout: 60*1000,
            type: "GET",
            url: url + queryString
        });
    }
}
function onSubmitSuccess(data, textStatus, jqXHR) {
	$.mobile.hidePageLoadingMsg();
    $("body").unmask();
	var submission = this;
    var model = submission.model;
    var rootPage;
	if ( submission.replace == 'all' ) {
        if ($("#loading").length==0) $("body").append('<div id="loading" data-role="page">Loading...</div>');
        $.mobile.changePage($("#loading"));
        setTimeout(function(){
            xforms.clear();
            $(data).find("[data-role='model']").each(function(){
                var script = this;
                var html = $(script).serializeXML();
                $("body").append(html);
            });
            $(data).find("[data-role='page']").each(function(){
                var page = this;
                rootPage = rootPage || page.id;
                var html = $(page).serializeXML();
                $("body").append(html);
            });
            loadXForms(document);
            $.mobile.changePage($("#" + rootPage));
            $("#" + rootPage).trigger("create");
        },700);
	} else if(submission.instance){
        var instance = model.instances[submission.instance];
        instance.content = $(data);
        var event = jQuery.Event("xforms-refresh");
        $(instance).trigger(event);
    }
    var event = jQuery.Event("xforms-submit-done");
    event.submission = submission;
    $(submission).trigger(event);
}
function domToQuery(node){
	var queryString = "";
	var rids = $(node).children("[restIndex]");
	rids.sort(function(a,b){
		return (parseInt($(a).attr('restIndex')) - parseInt($(b).attr('restIndex')));
	});
	rids.each(function(index){
		queryString += "/" + this.textContent;
	});
	var params = $(node).children(":not([restIndex])");
	params.each(function(index){
		if(index===0){
			queryString += "?";
		}
		queryString += this.tagName + "=" + this.textContent;
		if(index != params.length-1){
			queryString += "&";
		}
	});
	return queryString;
}